grammar Gramatica;

tokens {
	LLAVEIZQ = '{';
	LLAVEDER = '}';
	FLECHA = ':';
	FINPROD = ';';
}

@header {

package gramaticaANTLR;

import java.util.ArrayList;

import gramatica.Gramatica;
import gramatica.ParteDerecha;

import simbolo.Simbolo;
import simbolo.Terminal;
import simbolo.NoTerminal;

}

@lexer::header {
package gramaticaANTLR;
}

@lexer::members {
	public Token nextToken() {
		while (true) {
			this.token = null;
			this.channel = Token.DEFAULT_CHANNEL;
			this.tokenStartCharIndex = input.index();
			this.tokenStartCharPositionInLine = input.getCharPositionInLine();
			this.tokenStartLine = input.getLine();
			this.text = null;
			if ( input.LA(1)==CharStream.EOF ) {
				return Token.EOF_TOKEN;
			}
			try {
				mTokens();
				if ( this.token==null ) {
					emit();
				}
				else if ( this.token==Token.SKIP_TOKEN ) {
					continue;
				}
				return this.token;
			}
			catch (RecognitionException re) {
				reportError(re);
				throw new RuntimeException("Bailing out!"); // or throw Error
			}
		}
	}
}

@members {

Gramatica gramatica = new Gramatica();

public Gramatica getGramatica() {
	return gramatica;
}

/* Override exceptions... dejemos que las escupa.*/
protected void mismatch(IntStream input, int ttype, BitSet follow)
    throws RecognitionException
{
    throw new MismatchedTokenException(ttype, input);
}
public void recoverFromMismatchedSet(IntStream input,
                                     RecognitionException e,
                                     BitSet follow)
    throws RecognitionException
{
    throw e;
}

}

@rulecatch {
/* Manejo de excepciones */
catch (RecognitionException e) {
    throw e;
}
}


gramatica
	:	terminales {
			gramatica.setTerminales($terminales.terminales);
		}
		noTerminales {
			gramatica.setNoTerminales($noTerminales.noTerminales);
			gramatica.iniciarProducciones();
		}
		simboloInicial {
			gramatica.setSimboloInicial($simboloInicial.simboloInicial);
		}
		listaProducciones EOF;


terminales returns [ArrayList<Terminal> terminales]
	:	LLAVEIZQ
		listaTerminales {$terminales.terminales = $listaTerminales.terminales;}
		LLAVEDER;


listaTerminales returns [ArrayList<Terminal> terminales]
@init {
$listaTerminales.terminales = new ArrayList<Terminal>();
}
	:	(TERMINAL {
			terminales.add(new Terminal($TERMINAL.text));
		})* ;


noTerminales returns [ArrayList<NoTerminal> noTerminales]
	:	LLAVEIZQ
		listaNoTerminales {$noTerminales.noTerminales = $listaNoTerminales.noTerminales;}
		LLAVEDER;


listaNoTerminales returns [ArrayList<NoTerminal> noTerminales]
@init {
	$listaNoTerminales.noTerminales = new ArrayList<NoTerminal>();
}
	:	(NOTERMINAL {noTerminales.add(new NoTerminal($NOTERMINAL.text));})*;


simboloInicial returns [NoTerminal simboloInicial]
	:	NOTERMINAL {
				NoTerminal nt = new NoTerminal($NOTERMINAL.text);
				if (gramatica.getNoTerminales().indexOf(nt) != -1) {
					$simboloInicial.simboloInicial = nt;
				}
				else {
					System.err.println("Símbolo inicial no válido: " + 
							$NOTERMINAL.text + " no está en los no terminales");
					throw new RecognitionException();
				}
			};


listaProducciones
	:	produccion*;


produccion
	:	NOTERMINAL {
				NoTerminal nt = new NoTerminal($NOTERMINAL.text);
				if (gramatica.getNoTerminales().indexOf(nt) == -1) {
					System.err.println("Parte derecha inválida: " + 
							$NOTERMINAL.text + " no está en los no terminales");
					throw new RecognitionException();
				}
			}
		FLECHA
		parteDerecha {
			gramatica.definirProduccion(new NoTerminal($NOTERMINAL.text),
				$parteDerecha.parteDerecha);
		}
		FINPROD;


parteDerecha returns [ParteDerecha parteDerecha]
@init {
	ArrayList<Simbolo> simbolos = new ArrayList<Simbolo>();
	$parteDerecha.parteDerecha = new ParteDerecha();
}
@after {
	$parteDerecha.parteDerecha.setSimbolos(simbolos);
}
	:	(NOTERMINAL {
				NoTerminal nt = new NoTerminal($NOTERMINAL.text);
				if (gramatica.getNoTerminales().indexOf(nt) != -1) {
					simbolos.add(nt);
				}
				else {
					System.err.println("Símbolo no terminal no válido en parte derecha de producción: " + 
							$NOTERMINAL.text + " no está en los no terminales");
					throw new RecognitionException();
				}
			} |
		TERMINAL {
				Terminal t = new Terminal($TERMINAL.text);
				if (gramatica.getTerminales().indexOf(t) != -1) {
					simbolos.add(t);
				}
				else {
					System.err.println("Símbolo terminal no válido en parte derecha de producción: " + 
							$TERMINAL.text + " no está en los no terminales");
					throw new RecognitionException();
				}
			})*;



/* Reglas para el lexer */
TERMINAL
	:	'a'..'z';

NOTERMINAL
	:	'A'..'Z';

WS: (' ' | '\n' | '\r' | '\t')+ {$channel=HIDDEN;} ; // ignore whitespace
